using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;

using BizElements.Core.ProviderComponents;

namespace BizElements.Core
{
    /// <summary>
    /// Delegate to static method that provides an <see cref="IDbTable"/> object compatibile with the current entity.
    /// The method may build a new instance or return an existing one, depending on a usage scenario.
    /// </summary>
    /// <param name="alias">Optional table alias.</param>
    /// <returns>An instance of a class that implements <b>IDbTable</b> interface.</returns>
    public delegate IDbTable DbTableProvider(string alias);

	#region Documentation.
	/// <summary>
	/// Models data structure and behavior of a database entity.
	/// </summary>
	/// <remarks><p>Doesn't implement any data-access operations.</p>
	/// <p>Features:
	/// <list type="bullet">
	/// <item>Class members are mapped to table fields. An instance of the class represents a row in the database.</item>
	/// <item>Tracks current entity state in regard to data-source: new, synchronised, out-of-sync, deleted.</item>
	/// <item>Maps relations to parent entities. Inherited classes may implement data-acces logic for automatic relationship navigation.</item>
	/// <item>Validation.</item>
	/// <item>Conversion to/from the ADO.NET <see cref="DataRow"/> object.</item>
	/// </list></p>
	/// </remarks>
	#endregion
	[Serializable]
    public abstract class EntityModelBase : IEntity, IRecordTransformer, IEntityValidator, IEntityPropertyBinder, IEntityKeyBinder, ICloneable
    {
        #region Private members.

		/// <summary>
		/// Indicates the state of entity in regard to data-source.
		/// </summary>
		EntityState entityState;

		/// <summary>
		/// Enables or disables real-time null-check for non-nullable fields. Default is <b>true</b>.
		/// </summary>
		private bool nullCheckEnabled = true;

        /// <summary>
        /// Alias of the table/view to which this entity belongs to.
        /// Required as distinct member when deserializing object to recreate member 'table'.
        /// </summary>
        private string tableAlias;

		/// <summary>
		/// Class that holds meta data for the table/view this entity belongs to.
		/// Required as distinct member when deserializing object to recreate member 'table'.
		/// </summary>
		private Type dbTableClass;        

		/// <summary>
		/// Holds meta data for the table/view this entity belongs to.
		/// Not serialized because it is very big. This member should never be acessed directly.
		/// Use _Table property as it will recreate this member if it's null (eg. after deserialization).
		/// </summary>
		[NonSerialized]
		private IDbTable table;

		/// <summary>
		/// Indicates whether the entity may automatically navigate the database (when not explicitly ordered).
		/// </summary>
		/// <remarks><p>If entity relationship navigation is enabled, the <see cref="GetParent"/> method and 
		/// properties mapped to parent entities (via foreign keys) will automatically try to fetch the parent 
		/// entity if it isn't already fetched.</p>
		/// <p>Default is <b>true</b>.</p></remarks>
		bool relationshipNavigationEnabled = true;

        /// <summary>
        /// Optional delegate to optimized static method that builds 'table' member. Eg. used when deserializing entity object.
        /// </summary>
        DbTableProvider getDbTable;

		#endregion

		#region _Table.

		/// <summary>
		/// IDbTable class for the entity's table/view.
		/// </summary>
		protected IDbTable _Table
		{
			get
			{
				// Member 'table' may be null after deserialization. It has to be recreated.
				// In some cases, entity might still not know the table it belongs to (eg. while cloning).
                EnsureTable();
				return this.table;
			}
			private set
			{
				this.table = value;

                // Set info required by EnsureTable() method. Required when deserializing.
                this.dbTableClass = value.GetType();
                bool isAliasNotEqualToTableName = (value.Alias != value.TableName);
                this.tableAlias = (isAliasNotEqualToTableName) ? value.Alias : null;
			}
		}

        private void EnsureTable()
        {
            // Member 'table' may be null after deserialization. It has to be recreated.
            // In some cases, entity might still not know the table it belongs to (eg. while cloning).
            // Table initialized or don't know to which table entity belongs to.
            if (this.table != null)
                return;            
            
            if (this.getDbTable != null)
                this.table = this.getDbTable(this.tableAlias);
            else if (this.dbTableClass != null)
                this.table = DbTableFactory.CreateTable(this.dbTableClass, this.tableAlias);
        }

		#endregion

		#region Constructors.

		/// <summary>
		/// Initializes a new instance of EntityModelBase.
		/// </summary>
		/// <param name="dbTable">Metadata for table/view to which the entity belongs to.</param>
		/// <remarks>Sets table/view and the catalog to which this entity belongs to.</remarks>
		protected EntityModelBase(IDbTable dbTable)
		{
            this.entityState = EntityState.New;
			_Table = dbTable;
		}

        /// <summary>
        /// Initializes a new instance of EntityModelBase.
        /// </summary>
        /// <param name="dbTableClass">A class that implements <see cref="IDbTable"/> interface.</param>
        /// <param name="tableAlias">Optional table alias.</param>
        /// <param name="staticGetDbTableMethod">Optional static method that provide an instance of class specified in the <b>dbTableClass</b>. 
        /// If <b>null</b> then default implementation using reflection is used.</param>
        protected EntityModelBase(Type dbTableClass, string tableAlias, DbTableProvider staticGetDbTableMethod)
        {
            this.entityState = EntityState.New;
            if ((staticGetDbTableMethod != null) && !staticGetDbTableMethod.Method.IsStatic)
                throw new ArgumentException(Messages.EntityModelBase_TableProviderMustBeStaticMethod, "staticGetDbTableMethod");

            this.dbTableClass = dbTableClass;
            this.tableAlias = tableAlias;
            this.getDbTable = staticGetDbTableMethod;
        }

        /// <summary>
        /// Initializes a new instance of EntityModelBase.
        /// </summary>
        /// <param name="table">Metadata for table/view to which the entity belongs to.</param>
        /// <param name="dbTableClass">A class that implements <see cref="IDbTable"/> interface. Must be same as typeof(table) but this is not checked to ensure faster performance.</param>
        /// <param name="entityState">Specifies the state of entity with regard to data-source.</param>
        protected EntityModelBase(IDbTable table, Type dbTableClass, EntityState entityState)
        {
            // Skip _Table setter.
            this.table = table;
            this.dbTableClass = dbTableClass;
            this.entityState = entityState;
        }

		#endregion

		// IEntity implementation.

		#region IEntity properties.

		/// <summary>
		/// Gets IDbTable object that contains metadata for the entity's table/view.
		/// </summary>
		IDbTable IEntity.Table
		{
			get {return _Table;}
		}

		/// <summary>
		/// Gets or sets the value which indicates the state of entity in regard to data-source.
		/// </summary>
		public virtual EntityState EntityState
		{
			get {return this.entityState;}
			set {this.entityState = value;}
		}

		/// <summary>
		/// Gets or sets a value indicating whether the real-time null-check for non-nullable fields is performed.
		/// </summary>
		/// <remarks><p>When set to <b>true</b> getters mapped to table fields generate an exception if accessed 
		/// before a value has been set. When set to <b>false</b> getters return default values if accessed 
		/// before a value has been set (eg. zero for non-nullable integer fields).</p>
		/// <p>Default value is <b>true</b>.</p></remarks>
		/// <example>
		/// The following example demonstrates null-check behavior when accessing a value type field.
		/// <code>
		/// // The following code segment will throw an exception.
		/// // We are trying to access a non-nullable integer field before it has been set.
		/// EmployeesEntity emp = new EmployeesEntity();			
		/// emp.NullCheckEnabled = true;
		/// int id = emp.EmployeeID;
		/// </code>
		/// The following example demonstrates a value-type getter behavior when null-check is disabled.
		/// <code>
		/// // The following segment will set a zero in the id variable.
		/// // When the null-check is disabled non-nullable value getters will return a default value.
		/// EmployeesEntity emp = new EmployeesEntity();
		/// emp.NullCheckEnabled = false;
		/// int id = emp.EmployeeID;
		/// </code>
		/// </example>
		public bool NullCheckEnabled
		{
			get {return this.nullCheckEnabled;}
			set {this.nullCheckEnabled = value;}
		}

        /// <summary>
        /// Gets or sets the value which indicates whether the entity is allowed to automatically navigate the database via relations (when not explicitly ordered).
        /// </summary>
        /// <remarks><p>Has no affect on entity classes that don't implement data-access logic.</p>
        /// <p>If entity relationship navigation is enabled, the <see cref="GetParent"/> method and 
        /// properties mapped to parent entities (via foreign keys) will automatically try to fetch the parent 
        /// entity if it isn't already fetched.</p>
        /// <p>If entity relationship navigation is disabled, the <b>GetParent</b> method and 
        /// properties mapped to parent entities (via foreign keys) will not automatically access the data-source 
        /// to fetch the parent entity. Thus, if the parent isn't already initialized, <b>null</b> will be returned.</p>
        /// <p>Default behavior is <b>not</b> standardized. See documentation for classes that implement the IEntity 
        /// interface.</p></remarks>
        public virtual bool RelationshipNavigationEnabled
        {
            get { return this.relationshipNavigationEnabled; }
            set { this.relationshipNavigationEnabled = value; }
        }

		#endregion
		
		#region Conversion - IRecordTransformer implementation.

        /// <summary>Returns current instance.</summary>
        IEntity IRecordTransformer.TransformedEntity
        {
            get { return this; }
            set { }
        }

		/// <summary>
		/// Initializes entity members with data stored in the given DataRow.
		/// </summary>
		/// <param name="row">DataRow with the required columns.</param>
		/// <remarks>BizElements Generator generates constructors that create an entity from data contained 
		/// in a DataRow so this method is rarely invoked manually.</remarks>
		/// <example>
		/// The following example shows an example of generated constructor that creates an instance of EmployeesEntity class from a DataRow.
		/// <code>
		/// public class EmployeesEntity : EntityBase
		/// {
		///		public EmployeesEntity(DataRow row) : base(new EmployeesMeta())
		///		{
		///			FromDataRow(row);
		///		}
		/// }
		/// </code>
		/// </example>
		public abstract void FromDataRow(DataRow row);

        /// <summary>
        /// Initializes entity members with data stored in the given DataRow.
        /// </summary>
        /// <param name="row">DataRow with all or some of the columns defined in meta data.</param>
        /// <param name="fieldMetadata"><see cref="IDbTable"/> meta data object which links ADO.NET row columns to entity properties.</param>
        /// <remarks><p>This method, when used in combination with <see cref="IDbTable"/> objects that contain columns with
        /// prefixed aliases, enables you to initialize multiple entities that belong to different tables with data from a single DataRow. 
        /// This is particularly usefull when fetching an entity with one or more of its parent entities, since all required data can be
        /// retrieved in a single select statement.</p>
        /// <p>Generic implementation is not optimized, and should be overriden and optimized in derived classes. Latest version of BizElements generator generates
        /// optimized code. Generic implementation is only intended to be used for compatibility purposes, ie. if the code hasn't been regenerated.</p>
        /// </remarks>
        /// <example>The following example shows how to retrieve data required to initialize an EmployeesEntity
        /// and its EmployeesEntity.EmployeesSuperrior parent property.
        /// <code>
        /// public EmployeesEntity FetchEmployeesAndManager(int employeeId)
        /// {
        /// 	// Parent entity is from the same table.
        /// 	// Assign alises to tables and set prefixed aliases to all columns.
        /// 	// SELECT statement which is sent to the database looks like this:
        /// 	// SELECT [workers].[EmployeeID] AS [workers_EmployeeID], [workers].[LastName] AS [workers_LastName]...
        /// 	// This ensures that all items in the select list have unique aliases,
        /// 	// thus ensuring unique column names in the resulting ADO.NET DataRow.
        /// 	
        /// 	EmployeesMeta workers = new EmployeesMeta("workers", /*set column aliases*/ true);
        /// 	EmployeesMeta managers = new EmployeesMeta("managers", /*set column aliases*/ true);
        /// 	SelectStatement select = new SelectStatement(workers);
        /// 	select.SelectList.Add(workers.Columns);
        /// 	select.SelectList.Add(managers.Columns);
        /// 	select.Relations.Add(new DbRelation(managers, workers, workers.ReportsTo));
        /// 	select.Where.Add(workers.EmployeeID, employeeId);
        /// 	
        /// 	DataTable data = select.Execute();
        /// 	DataRow row = (data.Rows.Count > 0) ? data.Rows[0] : null;
        /// 	if (row == null)
        /// 		return null;
        /// 
        /// 	EmployeesEntity emp = new EmployeesEntity();
        /// 	emp.FromDataRow(row, workers);
        /// 	bool hasSuperrior = !emp.IsNull(emp.Table.ReportsTo);
        /// 	if (hasSuperrior)
        /// 	{
        /// 		EmployeesEntity reportsTo = new EmployeesEntity();
        /// 		reportsTo.FromDataRow(row, managers);
        /// 		emp.EmployeesSuperior = reportsTo;
        /// 	}
        /// 
        /// 	return emp;
        /// }
        /// </code>
        /// </example>
        public virtual void FromDataRow(DataRow row, IDbTable fieldMetadata)
        {
            IDbColumn currentColumn;
            object currentColumnValue;
            foreach (IDbColumn entityField in _Table.Columns)
            {
                currentColumn = fieldMetadata.Columns.GetByPropertyName(entityField.PropertyName);
                currentColumnValue = (currentColumn != null) ? GetColumnValue(row, currentColumn) : null;
                if (currentColumnValue != null)
                    SetField(entityField, currentColumnValue);
                else
                    ResetField(entityField);
            }
        }

        private static object GetColumnValue(DataRow row, IDbColumn column)
        {
            int colIdx = row.Table.Columns.IndexOf(column.Alias);
            if (colIdx < 0)
                colIdx = row.Table.Columns.IndexOf(column.ColumnName);
            if (colIdx < 0)
                throw new ArgumentException(Messages.EntityModelBase_TableDoesntContainSpecifiedColumn + column.ColumnName, "column");

            return row[colIdx];
        }

		/// <summary>
		/// Copies the data contained in the entity to a DataRow object.
		/// </summary>
		/// <returns>DataRow.</returns>
		/// <example>
		/// The following example creates an entity object, sets some of its fields and converts it to a DataRow object.
		/// <code>
		/// public DataRow EmployeeToDataRow()
		/// {
		/// 	// Create a new EmployeesEntity. Set first and last name.
		/// 	EmployeesEntity emp = new EmployeesEntity();
		/// 	emp.FirstName = "John";
		/// 	emp.LastName = "Smith";
		/// 
		/// 	// Convert the entity to a DataRow object.
		/// 	// Columns that haven't been set contain a DBNull.Value.
		/// 	DataRow row = emp.ToDataRow();
		/// 	return row;			
		/// }
		/// </code>
		/// </example>
        [Obsolete("The method is scheduled for removal in future versions. Use EntityCollectionExtensions.ToDataTable() instead.")]
        public virtual DataRow ToDataRow()
        {
            object[] values = ToObjectArray();
            for (int i = 0; i < values.Length; i++)
            {
                if (values[i] == null)
                    values[i] = DBNull.Value;
            }

            DataTable table = _Table.NewDataTable();
            table.Rows.Add(values);
            return table.Rows[0];
        }

		/// <summary>
		/// Creates an array of objects containing entity data.
		/// </summary>	
		/// <returns>Entity values.</returns>
		/// <example>
		/// The following example creates an entity object, sets some of its fields and converts it to an array of objects.
		/// <code>
		/// public object[] EmployeeToObjects()
		/// {
		/// 	// Create a new EmployeesEntity. Set first and last name.
		/// 	EmployeesEntity emp = new EmployeesEntity();
		/// 	emp.FirstName = "John";
		/// 	emp.LastName = "Smith";
		/// 
		/// 	// Convert the entity to an array of objects.
		/// 	// Fields that haven't been set are represented with nulls.
		/// 	object[] values = emp.ToObjectArray();
		/// 	return values;	
		/// }
		/// </code>
		/// </example>
		public abstract object[] ToObjectArray();

		/// <summary>
		/// Initializes entity members with the given values.
		/// </summary>
		/// <param name="entityValues">Array with the required values.</param>
		public abstract void FromObjectArray(object[] entityValues);

		#endregion		

		#region PK.

        /// <summary>Returns current instance.</summary>
        IEntity IEntityKeyBinder.EntityWithKey
        {
            get { return this; }
            set { }
        }

		/// <summary>
		/// Gets the value(s) that uniquely identify an entity.
		/// In the order as specified in the accompanying IDbTable class.
		/// <b>Null</b> if the parent table/view doesn't have a primary key constraint or the required fields are not set.
		/// </summary>
		/// <returns>An object array that holds Primary Key values.</returns>
		public virtual object[] GetPrimaryKeyValue()
		{
			// Inherited classes should implement optimized method.
	
			DbColumnCollection pkFields = _Table.PrimaryKey;				
			object[] entityValues = ToObjectArray();
			
			if (pkFields.Count == 1)
			{
				// One PK field.
				object pkValue = entityValues[pkFields[0].Ordinal];
				if (pkValue != null)
					// Return PK.
					return new object[]{entityValues[pkFields[0].Ordinal]};
				else
					// PK is not set.
					return null;					
			}
			else if (pkFields.Count == 0)
			{
				// Table/view doesn't have a primary key constraint.
				return null;
			}
			else
			{
				// Multiple PK fields.
				ArrayList pkValues = new ArrayList();
				foreach (IDbColumn pkField in pkFields)
				{
					object currPkValue = entityValues[pkField.Ordinal];
					if (currPkValue != null)
					{
						pkValues.Add(currPkValue);
					}
					else
					{
						// PK is not set.
						return null;
					}
				}

				object[] values = new object[pkValues.Count];
				for (int valIdx=0; valIdx<values.Length; valIdx++)
					values[valIdx] = pkValues[valIdx];

				return values;
			}			
		}

		/// <summary>
		/// Checks if the given values are equal to the primary key value of this entity.
		/// </summary>
		/// <param name="values">Values.</param>
		/// <returns>True if primary key values match the given values; false otherwise or if the PK fields 
		/// of this entity aren't set.</returns>
		public virtual bool PrimaryKeyEquals(object[] values)
		{
			// Inherited classes should implement optimized methods without loops.			
			object[] pk = this.GetPrimaryKeyValue();
			if (pk == null  ||  values == null  ||  pk.Length != values.Length)
				return false;
			
			for (int valIdx=0; valIdx<pk.Length; valIdx++)
			{
				if (pk[valIdx].Equals(values[valIdx]) == false)
					return false;
			}			

			return true;
		}

		#endregion

		#region Validation.

        /// <summary>Returns current instance.</summary>
        IEntity IEntityValidator.ValidatedEntity
        {
            get { return this; }
            set { }
        }

		/// <summary>
		/// Validates all fields of an entity. Returns all encountered errors.
		/// </summary>
		/// <returns>Array of FieldError objects that describe all encountered errors. Empty array if there are no errors.</returns>
		/// <example>
		/// The following example creates a new empty EmployeesEntity object and validates it.
		/// <code>
		/// public string ValidateEmployee()
		/// {
		/// 	// Create a new empty EmployeesEntity and validate it.
		/// 	// Employees table constraints specify that fields LastName and FirstName are not nullable.
		/// 	// Validation will return an array that contains two errors (LastName and FirstName may not be null).
		/// 	EmployeesEntity emp = new EmployeesEntity();
		/// 	FieldError[] errors = emp.ValidateAllFields();
		/// 
		/// 	// Iterate through all errors and create a message for the user that describes the errors ecountered.
		/// 	string message = "";
		/// 	foreach (FieldError error in errors)
		/// 	{
		/// 		if (error.ErrorCode == FieldErrorCode.NullError)
		/// 			message += error.Field.PropertyName + " property may not be null. ";
		/// 		else
		/// 			message += error.Field.PropertyName + " contains an invalid value. ";
		/// 	}
		/// 
		/// 	return message;
		/// }
		/// </code>
		/// </example>
		public virtual FieldError[] ValidateAllFields()
		{
			ArrayList errorList = new ArrayList();
			object[] entityValues = ToObjectArray();
			for (int fieldIdx=0; fieldIdx<_Table.Columns.Count; fieldIdx++)
			{				
				FieldError currError = new FieldError(_Table.Columns[fieldIdx], entityValues[fieldIdx]);
				if (currError.ErrorCode != FieldErrorCode.AllOk)				
					errorList.Add(currError);				
			}
			
			FieldError[] errorArray = new FieldError[errorList.Count];
			for (int errIdx=0; errIdx<errorArray.Length; errIdx++)
				errorArray[errIdx] = (FieldError) errorList[errIdx];

			return errorArray;
		}

		/// <summary>
		/// Returns a value which indicates whether all fields of an entity contain valid values. 
		/// </summary>
		/// <returns>True if all fields are valid; false otherwise.</returns>
		/// <remarks>This method is faster than ValidateAllFields() because it stops validation when the first error is found.</remarks>
		public virtual bool IsValidEntity()
		{			
			object[] entityValues = ToObjectArray();
			for (int fieldIdx=0; fieldIdx<_Table.Columns.Count; fieldIdx++)
			{
				object currValue = entityValues[fieldIdx];
				IDbColumn currColumn = _Table.Columns[fieldIdx];
                FieldValidator fieldValidator = new FieldValidator(currColumn);
                FieldErrorCode errCode = fieldValidator.Validate(currValue);
				if (errCode != FieldErrorCode.AllOk)
					return false;
			}

			return true;			
		}

		/// <summary>
		/// Validates specified field.
		/// </summary>
		/// <remarks>Checks whether the value contained in the given field obeys basic constraints 
		/// (nullability, min/max value etc). Returns <see cref="FieldErrorCode"/> that indicates 
		/// the type of error encountered.</remarks>
		/// <param name="field">Field mapped in this entity. Throws exception if an invalid field is given.</param>
		/// <returns>Error code that indicates the type of encountered error.</returns>
		public virtual FieldErrorCode ValidateField(IDbColumn field)
		{
			// Note: this generic implementation has a lot of overhead.
			// It is recommended to implement optimized ValidateField method in inherited classes.

			if (_Table.Columns.GetByColumnName(field.ColumnName) == null)
                throw new ArgumentException(Messages.EntityModelBase_SpecFieldDoesntExistInEntity, "field");
			
			object fieldValue = GetField(field);
			FieldError err = new FieldError(field, fieldValue);
			return err.ErrorCode;			
		}

		/// <summary>
		/// Returns a value which indicates whether the specifified is valid.
		/// Checks whether the value contained in the given field obeys basic constraints (nullability, min/max value etc).
		/// </summary>
		/// <param name="field">Field mapped in this entity. Throws exception if an invalid field is given.</param>
		/// <returns>True if the field is valid; false otherwise.</returns>
		public virtual bool IsValidField(IDbColumn field)
		{
			FieldErrorCode error = ValidateField(field);
			if (error == FieldErrorCode.AllOk)
				return true;
			else
				return false;
		}

		#endregion

		#region Field getters and setters - IEntityPropertyBinder.

        /// <summary>Returns current instance.</summary>
        IEntity IEntityPropertyBinder.BoundEntity
        {
            get { return this; }
            set { }
        }

		/// <summary>
		/// Resets the specified field to default value. Same as using SetField method with null argument.
		/// </summary>
		/// <param name="field">Field that is to be reset. Throws exception if an invalid field is given.</param>
		public virtual void ResetField(IDbColumn field)
		{
			SetField(field, null);
		}

		/// <summary>
		/// Sets the given value into specified field.
		/// </summary>
		/// <param name="field">Field. Throws exception if an invalid field is given.</param>
		/// <param name="value">Value. If null then the field is reset to default value.</param>
		public virtual void SetField(IDbColumn field, object value)
		{
			// Note: this generic implementation has a lot of overhead.
			// It is recommended to implement optimized SetField method in inherited classes.

			if (_Table.Columns.GetByColumnName(field.ColumnName) == null)
                throw new ArgumentException(Messages.EntityModelBase_SpecFieldDoesntExistInEntity, "field");

			object[] entityValues = ToObjectArray();
			entityValues[field.Ordinal] = value;
			FromObjectArray(entityValues);
		}

		/// <summary>
		/// Gets the value contained in the specified field.
		/// </summary>
		/// <param name="field">Field. Throws exception if an invalid field is given.</param>
		/// <returns>Value.</returns>
		public virtual object GetField(IDbColumn field)
		{
			// Note: this generic implementation has a lot of overhead.
			// It is recommended to implement optimized GetField method in inherited classes.

			if (_Table.Columns.GetByColumnName(field.ColumnName) == null)
                throw new ArgumentException(Messages.EntityModelBase_SpecFieldDoesntExistInEntity, "field");

			object[] entityValues = ToObjectArray();
			return entityValues[field.Ordinal];
		}

		/// <summary>
		/// Checks whether the specified field contains a <b>null</b> reference or it hasn't been initialized.
		/// </summary>
		/// <param name="field">Field. Throws exception if an invalid field is given.</param>
		/// <returns><b>true</b> if the field contains <b>null</b> not initialized; <b>false</b> otherwise.</returns>
		public virtual bool IsNull(IDbColumn field)
		{
			object value = GetField(field);
			bool isNull = (value == null);
			return isNull;
		}

		#endregion

        #region Relations - IEntityPropertyBinder.

        /// <summary>
		/// Gets the parent entity defined by the given foreign key.
		/// <seealso cref="RelationshipNavigationEnabled"/>
		/// </summary>
		/// <param name="foreignKey">FK. Child table of the given FK must be the current entity's table; 
		/// otherwise an exception is generated.</param>
		/// <returns>Parent entity. <b>Null</b> if the FK fields haven't been set or if the entity with the given key values doesn't exist.</returns>
		/// <remarks>If the entity hasn't been initialized yet but the FK values are set and relationship navigation
		/// is enabled then the method will automatically fetch it from the data source.</remarks>
		public abstract IEntity GetParent(DbRelation foreignKey);

		/// <summary>
		/// Sets the given value into the member that represents the parent entity defined by the foreign key.
		/// </summary>
		/// <param name="foreignKey">FK. Child table of the given FK must be the current entity's table; 
		/// otherwise an exception is generated.</param>
		/// <param name="entity">Parent entity. Must be compatibile with the targeted member.</param>
		/// <remarks>Inherited classes must implement the required logic.</remarks>
		public abstract void SetParent(DbRelation foreignKey, IEntity entity);

		#endregion

		#region ICloneable and CopyFrom.

		/// <summary>
		/// Creates a deep copy clone of current instance.
		/// </summary>
        /// <remarks>Copies all class members. Parent entities are not copied by reference, but cloned (deep copy).</remarks>
		public virtual object Clone()
		{
			EntityModelBase clone = (EntityModelBase)Activator.CreateInstance(this.GetType());
			clone.FromExistingEntity(this);
			return clone;
		}

		/// <summary>
		/// Initializes entity members with all data stored in the given entity.
		/// </summary>
		/// <param name="existing">Instance of IEntity. Must be compatibile with current entity; otherwise an exception is generated.</param>
		/// <remarks>Copies all class members. Parent entities are not copied by reference, but cloned (deep copy).</remarks>
		protected virtual void FromExistingEntity(IEntity existing)
		{
			if (_Table == null)		
				_Table = existing.Table.Clone(null);

            // throw new ArgumentException("Given entity doesn't belong to same table as current entity. Expected " + _Table.TableName + ", but " + existing.Table.TableName + " was provided.", "existing");
			if (_Table.TableName != existing.Table.TableName)
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Messages.EntityModelBase_EntityNotFromSameTableExpextedXButGotY, _Table.TableName, existing.Table.TableName), "existing");

			// Clone meta. 'this.table' will be instantiated when _Table getter is accessed for the first time.
			this.tableAlias = existing.Table.Alias;
			this.dbTableClass = existing.Table.GetType();
			this.table = null;			

			// Members mapped to fields.
			object[] entityValues = CloneObjectArray(existing.ToObjectArray());
			FromObjectArray(entityValues);

			// Parents. Temporary disable relation navigation to prevent possible data-access when cloning parents.
            bool relationNavigationStatusBeforeParentCloning = false;
            EntityModelBase classicEntity = existing as EntityModelBase;
            if (classicEntity != null)
            {
                relationNavigationStatusBeforeParentCloning = classicEntity.RelationshipNavigationEnabled;
                classicEntity.RelationshipNavigationEnabled = false;
            }
			
			foreach (DbRelation fk in _Table.ForeignKeys)
			{
				IEntity parent = existing.GetParent(fk);
				// Only set if not null (it's already null) because when setting parent entity property to NULL
				// then FK field would also nulled, thus undoing the previous step (field cloning) and causing error.
				if (parent != null)
					this.SetParent(fk, parent.Clone() as IEntity);
			}

            // Behavior. Entity state, relation navigation.
			// Restore original status of relationship navigation.
            if (classicEntity != null)
            {
                classicEntity.RelationshipNavigationEnabled = relationNavigationStatusBeforeParentCloning;
                this.RelationshipNavigationEnabled = classicEntity.RelationshipNavigationEnabled;
            }

            this.EntityState = existing.EntityState;
		}

		private static object[] CloneObjectArray(object[] values)
		{
			object[] clonedArray = new object[values.Length];

			// Object array may contain arrays and objects (eg. byte[] for BLOBs) that have to be cloned.
			for (int valueIdx=0; valueIdx < values.Length; valueIdx++)
			{
				// Cloning is not required for strings and value types.
				// Try to clone everything else.
				object currentValue = values[valueIdx];
				object clonedValue;
                
                Array currArray = currentValue as Array;
                if (currArray != null)
				{
					// Shallow copy of array will be OK because it contains only value types (usually bytes).
                    clonedValue = currArray.Clone();
				}
				else
				{
                    // Use Clone() method for array, otherwise just use = operator.
					clonedValue = currentValue;
				}

				clonedArray[valueIdx] = clonedValue;
			}

			return clonedArray;
		}

		#endregion
	}
}
